package com.google.code.opengw.server.domain;


import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.UUID;

import com.google.code.opengw.http.HTTPConstants;
import com.google.code.opengw.http.HTTPException;
import com.google.code.opengw.http.HTTPHeader;
import com.google.code.opengw.http.HTTPMethod;
import com.google.code.opengw.http.IOSocket;
import com.google.code.opengw.http.Request;
import com.google.code.opengw.http.Response;
import com.google.code.opengw.http.Connection;
import com.google.code.opengw.server.Server;
import com.google.code.opengw.server.support.ConfigurationUtils;
import com.google.code.opengw.server.support.FileUtils;
import com.google.code.opengw.support.OpenGWException;
import com.google.code.opengw.support.OpenGWLogger;

public class Worker implements Runnable{

	private String uuid;
	private Socket socket;
	private Route route;
	private Sync sync;
	private final WorkerManager manager;
	private final Domain domain;
	private final Server server;
	
	public Worker(Domain domain, WorkerManager manager){
		server = Server.getInstance();
		UUID uuid = UUID.randomUUID();
		this.uuid = uuid.toString();
		socket = null;
		route = null;
		this.domain = domain;
		this.manager = manager;
		this.sync = new Sync();
	}
	
	@Override
	public void run() {
		try{
				
			synchronized (sync) {
				deActivate();
				boolean keepAlive = false;
				IOSocket ioSocket = null;
				do{
					try{
						Request request = null;
						Response response = null;						
						try{
							
							InputStream inputStream = socket.getInputStream();
							byte[] buffer = new byte[server.getReadBufferSize()];
							int bufferIndex = 0;
							int bufferReadLength = 0;
							StringBuilder line = new StringBuilder("");
							boolean firstLine = false;
							boolean headerComplete = false;
							request = new Request(socket.getLocalAddress().getHostAddress());
							while(!headerComplete){
								bufferReadLength = inputStream.read(buffer);
								for( ; bufferIndex<bufferReadLength ; bufferIndex++ ){
									char ch = (char) buffer[bufferIndex];
									line.append(ch);
									if(ch == '\n'){
										if(line.toString().equals("\r\n")){
											headerComplete = true;
											bufferIndex++;
											break;
										}
										if(!firstLine){
											String[] firstLineArr = line.toString().split(" ");
											request.setMethod(HTTPMethod.valueOf(firstLineArr[0].toUpperCase()));
											String requestPath = firstLineArr[1];
											request.setProtocol((firstLineArr[2]).trim());
											int paramInd = requestPath.indexOf("?");
											if(paramInd != -1){
												request.addParams(requestPath.substring((paramInd + 1)));
												requestPath = requestPath.substring(0, paramInd);
											}
											
											route = domain.getRoute(requestPath);
											if(route.getRouteType() == RouteType.ROUTE){
												request.setHost(route.getRouteHost());
												String pathExtension = requestPath.equals(route.getRequestPath()) ? "" : requestPath.substring(route.getRequestPath().length());
												if(!route.isPathExtensionAvaliable() && !pathExtension.equals("")) {
													throw new OpenGWException(OpenGWException.NOT_FOUND_REQUEST_URL, HTTPConstants.CODE_404, requestPath);
												} else {
													if(!pathExtension.equals("") 
															&& !pathExtension.equals("/") 
															&& pathExtension.startsWith("/") 
															&& route.getRoutePath().endsWith("/"))
														request.setUri(route.getRoutePath() + pathExtension.substring(1));
													else if(!pathExtension.equals("") 
															&& !pathExtension.startsWith("/") 
															&& !route.getRoutePath().endsWith("/"))
														request.setUri(route.getRoutePath() + "/" + pathExtension);
													else
														request.setUri(route.getRoutePath() + pathExtension);;
												} 
											}
											
											firstLine = true;
										} else{
											HTTPHeader header = new HTTPHeader(line.toString());
											request.addHeader(header);
										}
										line = new StringBuilder("");
									}
								}
							}
							
							if(!route.controlRequestMethod(request.getMethod().toString())){
								throw new OpenGWException(OpenGWException.METHOD_NOT_ALLOWED, 405, request.getMethod().toString());
							}
						
							if(request.getContentLength() > 0 && request.getMethod() == HTTPMethod.POST){
								byte[] content = new byte[request.getContentLength()];
								int readContentLength = 0;
								if(bufferReadLength != bufferIndex){
									System.arraycopy(buffer, bufferIndex, content, 0, bufferReadLength - bufferIndex);
									readContentLength += (bufferReadLength - bufferIndex);
								}
								
								while(readContentLength < request.getContentLength()){
						            int len = inputStream.read(content, readContentLength, request.getContentLength() - readContentLength);
						            readContentLength += len;
						        }
								
								request.setContent(content);
							}
							
							buffer = null;
							
							if(route.getRouteType() == RouteType.ROUTE && route.isPolicyExists()){
								ioSocket = new IOSocket(route.getRouteHost(), route.getRoutePort(), route.getConnectionTimeout(), route.isRouteSecure());
								Connection connection = new Connection(ioSocket, server.getReadTimeout(), server.getConnectionBufferSize()); 
								response = connection.invoke(request);
								connection = null;
								keepAlive = response.isKeepAlive();
							} else if(route.getRouteType() == RouteType.ROUTE){
								ioSocket = new IOSocket(route.getRouteHost(), route.getRoutePort(), route.getConnectionTimeout(), route.isRouteSecure());
								Connection connection = new Connection(ioSocket, server.getReadTimeout(), server.getConnectionBufferSize());
								connection.invoke(request, socket);
								connection = null;
							} else if(route.getRouteType() == RouteType.RESOURCE){
								response = new Response(route.getContentType(), FileUtils.readFile(server.getPublishResourcePath(domain.getName(), route.getPublishName(), route.getResourceURL())));
								keepAlive = request.isKeepAlive();
								response.setKeepAlive(keepAlive);
							} else if(route.getRouteType() == RouteType.CUSTOM){
								
							}
						} catch (HTTPException e) {
							OpenGWLogger.addError(this.getClass(), e.getMessage(), e);
							response = new Response(e.getHttpCode());
						} catch (OpenGWException e) {
							OpenGWLogger.addError(this.getClass(), e.getMessage(), e);
							response = new Response(e.getHttpCode());
						} catch (Exception e) {
							if(e instanceof SocketTimeoutException){
								response = new Response(HTTPConstants.CODE_500, HTTPConstants.CODE_408);
								keepAlive = false;
							} else {
								response = new Response(HTTPConstants.CODE_500);
								keepAlive = false;
							}
							
							OpenGWLogger.addError(this.getClass(), e.getMessage(), e);
						}
						//SEND RESPONSE
						if(route == null || !(route.getRouteType() == RouteType.ROUTE && !route.isPolicyExists())){
							socket.getOutputStream().write(response.getHTTPHeaderString(false).getBytes());
							if(response.getContent() != null){
								socket.getOutputStream().write(response.getContent());
							}
							socket.getOutputStream().flush();
						}
						//SEND RESPONSE
						//Close connection
						request = null;
						response = null;
						
					} catch (Exception e) {
						OpenGWLogger.addError(this.getClass(), e.getMessage(), e);
					} finally {
						try{
							socket.close();
							if(ioSocket != null)
								ioSocket.close();
						} catch (Exception e) {							
							OpenGWLogger.addError(this.getClass(), e.getMessage(), e);
						}
					}
					socket = null;
					route = null;
					ioSocket = null;
					manager.changeWorkerState(this);
					deActivate();
					
				} while(socket != null);
			}
			
		} catch (Exception e) {
		} finally{
			
		}
	}
	
	public synchronized void activate(){
		synchronized (sync) {
			sync.notify();
		}
	}
	
	public void deActivate(){
		try{
			sync.wait();
		} catch (Exception e) {
		}
	}

	public void setUuid(String uuid) {
		this.uuid = uuid;
	}

	public String getUuid() {
		return uuid;
	}
	
	public void setSocket(Socket socket){
		if(this.socket == null)
			this.socket = socket;
	}
	
	public boolean isAvaliable(){
		if(socket == null)
			return true;
		else 
			return false;
	}
	
	
	@Override
	public String toString() {
		return socket.toString();
	}
	
	private class Sync{
		
	}

}
